package com.gjy.encryption.ukey;

import javax.crypto.Cipher;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

/**
 * 证书组件
 * 证书管理: KeyStore & OpenSSL
 * <p>
 * KeyStore:
 * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/keytool.html">keytool</a>
 * 生成本地数字证书:
 * keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias https://coder-gjy.work/ -keystore coder-gjy.keystore
 * keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias https://coder-gjy.work/ -keystore coder-gjy.keystore -dname "CN=coder-gjy, OU=coder, O=coder, L=NanJing, ST=JiangSu, C=CN" -storepass coder-gjy@123
 * password: coder-gjy@123
 * 导出数字证书:
 * keytool -exportcert -alias https://coder-gjy.work/ -keystore coder-gjy.keystore -file coder-gjy.cer -rfc -storepass coder-gjy@123
 * 生成数字证书签发申请:
 * keytool -certreq -alias https://coder-gjy.work/ -keystore coder-gjy.keystore -file coder-gjy.csr -v -storepass coder-gjy@123
 * 导入数字证书:
 * coder-gjy.cer 是由数字证书颁发认证机构签发数字证书
 * keytool -importcert -trustcacerts -alias https://coder-gjy.work/ -file coder-gjy.cer -keystore coder-gjy.keystore -storepass coder-gjy@123
 *
 * @author 宫静雨
 * @version 1.0
 * @since 2022-11-09 18:52:42
 */
public class CertificateCoder {
    // 类型证书 X.509
    private static final String CERT_TYPE = "X.509";

    /**
     * 获得KeyStore
     *
     * @param path 秘钥库路径
     * @param pwd  密码
     * @return 秘钥库
     */
    private KeyStore getKeyStore(String path, String pwd) {
        try (FileInputStream fis = new FileInputStream(path)) {
            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(fis, pwd.toCharArray());
            return ks;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 由 KeyStore 获得私钥
     *
     * @param path     秘钥库路径
     * @param alias    别名
     * @param password 密码
     * @return 私钥
     */
    private PrivateKey getPrivateKeyByKeyStore(String path, String alias, String password) {
        try {
            KeyStore ks = getKeyStore(path, password);
            return (PrivateKey) ks.getKey(alias, password.toCharArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获得 Certificate
     *
     * @param path  秘钥库路径
     * @param alias 别名
     * @param pwd   密码
     * @return Certificate 证书
     */
    private Certificate getCertificate(String path, String alias, String pwd) {
        try {
            KeyStore ks = getKeyStore(path, pwd);
            return ks.getCertificate(alias);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获得 Certificate
     *
     * @param certificatePath 证书路径
     * @return 证书
     */
    private Certificate getCertificate(String certificatePath) {
        try (FileInputStream fis = new FileInputStream(certificatePath)) {
            CertificateFactory certificateFactory = CertificateFactory.getInstance(CERT_TYPE);
            return certificateFactory.generateCertificate(fis);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 由 Certificate 获得公钥
     *
     * @param certificatePath 证书路径
     * @return 公钥
     */
    private PublicKey getPublicKeyByCertificate(String certificatePath) {
        return getCertificate(certificatePath).getPublicKey();
    }

    public byte[] encryptByPub(byte[] data, String certificate) {
        try {
            PublicKey publicKey = getPublicKeyByCertificate(certificate);
            Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] decryptByPub(byte[] data, String certificate) {
        try {
            PublicKey publicKey = getPublicKeyByCertificate(certificate);
            Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] encryptByPri(byte[] data, String keystore, String alias, String password) {
        try {
            PrivateKey privateKey = getPrivateKeyByKeyStore(keystore, alias, password);
            Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] decryptByPri(byte[] data, String keystore, String alias, String password) {
        try {
            PrivateKey privateKey = getPrivateKeyByKeyStore(keystore, alias, password);
            Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] sign(byte[] sign, String keystore, String alias, String password) {
        try {
            X509Certificate x509Certificate = (X509Certificate) getCertificate(keystore, alias, password);
            Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
            PrivateKey privateKey = getPrivateKeyByKeyStore(keystore, alias, password);
            signature.initSign(privateKey);
            signature.update(sign);
            return signature.sign();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public boolean verify(byte[] data, byte[] sign, String certificate) {
        try {
            X509Certificate x509Certificate = (X509Certificate) getCertificate(certificate);
            Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
            signature.initVerify(x509Certificate);
            signature.update(data);
            return signature.verify(sign);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
